#include "OP_Scale.h"

using namespace std;
using namespace CICore;
using namespace CIGeom;
using namespace CIView;

///<************************************************************************************************************>
///<************************************* region CONSTANTS DEFINITION ******************************************>
///<************************************************************************************************************>

/***************************************************************************************************************
************************************** endregion CONSTANTS DEFINITION ******************************************
****************************************************************************************************************/












/// <summary> 
/// Constructor 
/// </summary>
/// <param name="v"> A reference to the vector that contains the four 3D views pointers </param>
CIOps::OP_Scale::OP_Scale (vector<CIView::View*>& v)
: Operation (v), feedbackPos (CIView::View::Feedback_Scaling.GetPosition()), feedbackToBeRestored (OperationManager::Origin) {}

/// <summary> 
/// Destructor 
/// </summary>
CIOps::OP_Scale::~OP_Scale() {}









///<************************************************************************************************************>
///<**************************************** region Utility Methods ********************************************>
///<************************************************************************************************************>

/// <summary> 
/// Method called to execute directly the operation 
/// </summary>
/// <returns> The operation status </returns>
int CIOps::OP_Scale::Exec() {
    Vector3D& ScaleParams = OperationManager::X_Y_Z_Params;

    if ((ScaleParams.x == 1 && ScaleParams.y == 1 && ScaleParams.z == 1) 
        || (ScaleParams.x == 0 || ScaleParams.y == 0 || ScaleParams.z == 0)) {
            status = ZOMBIE;
            return status;
    }

    //Temporary matrix used to compute the final scaling tranformation
    Matrix toOrigin, toPosition, scaling;    
    scaling.SetScaling (ScaleParams.x, ScaleParams.y, ScaleParams.z);               //The scaling to apply 

    if (OperationManager::CenterOfMass) {
        toPosition.SetTranslation (feedbackPos.x, feedbackPos.y, feedbackPos.z);        //Sets up the translation to the final position            
        toOrigin.SetTranslation (-feedbackPos.x, -feedbackPos.y, -feedbackPos.z);       //Sets up the translation to the origin        
        scalingMatrix = toOrigin * scaling * toPosition;                                //Computes the final matrix for the correct scaling transformation

        scene.ApplyMatrix (scalingMatrix);                                              //Applies the transformation to the selected vertices
    } 
    else if (OperationManager::UserPoint) {
        feedbackToBeRestored = true;

        toPosition.SetTranslation (feedbackPos.x, feedbackPos.y, feedbackPos.z);        //Sets up the translation to the final position            
        toOrigin.SetTranslation (-feedbackPos.x, -feedbackPos.y, -feedbackPos.z);       //Sets up the translation to the origin        
        scalingMatrix = toOrigin * scaling * toPosition;                                //Computes the final matrix for the correct scaling transformation

        scene.ApplyMatrix (scalingMatrix);                                              //Applies the transformation to the selected vertices 
    }
    else {
        feedbackToBeRestored = true;
        scalingMatrix = scaling;

        scene.ApplyMatrix (scalingMatrix);                         //Applies the transformation to the selected vertices
        RestoreFeedbackPosition();
    }

    inverseMatrix = scalingMatrix.GetInverse();                 //Computes the new inverse matrix (used to undo the transformation)

    UpdateViews();                                              //Updates the 3D views

    status = COMPLETED;
    
    return status;         
}

/// <summary> 
/// Method called to undo the operation 
/// </summary>
void CIOps::OP_Scale::Undo() {
    status = UNDO;

    scene.ApplyMatrix (inverseMatrix);  //Applies the last inverse matrix

    if (feedbackToBeRestored) 
        RestoreFeedbackPosition();

    UpdateViews();                      //Updates the 3D views
}

/// <summary> 
/// Method called to redo the operation 
/// </summary>
void CIOps::OP_Scale::Redo() {
    status = COMPLETED;

    scene.ApplyMatrix (scalingMatrix);  //Applies the last scaling matrix

    if (feedbackToBeRestored) 
        RestoreFeedbackPosition();

    UpdateViews();                          //Updates the 3D views
}

/// <summary> 
/// Mouse Pressed event handler, called whenever the operation 
/// is in execution and a 3D view intercepts a mouse press event
/// </summary>
/// <param name="view"> The view that has thrown the event </param>
/// <param name="e"> Mouse Pressed event </param>
void CIOps::OP_Scale::MousePressEvent (CIView::View* view, QMouseEvent* e) {
    float x = e->x();
    float y = e->y();

    if (OperationManager::UserPoint) {
        CIView::View::Feedback_Scaling.SelectAll();
        selected = FeedbackScaling::ALL_AXES;
    }    
    else
        selected = view->SelectFeedbackScalingAxes (x, y);         //Selects the axes according to the mouse click2D coordinates
    
    view->WindowToWorldCoordinates (&x, &y);                    //Unproject the mouse cursor coordinates

    //Computing the mouse click coordinates (in world coordinates)
    switch (view->GetCameraType()) {
        case Camera::TOP:
            click.x = x;
            click.z = y;
            break;
        
        case Camera::FRONT:
            click.x = x;
            click.y = y;
            break;
        
        case Camera::RIGHT:            
            click.y = y;
            click.z = x;
            break;
        
        case Camera::USER:          
            click.x = x;
            click.y = y;
            break;
    }

    Orientation& cameraOrient = view->GetCameraOrientation();

    if (OperationManager::UserPoint) {
        feedbackToBeRestored = true;

        //Computing the feedback position according to the user click coordinates
        switch (view->GetCameraType()) {
            case Camera::TOP:
                feedbackPos.x = x;
                feedbackPos.z = y;                
                break;
            
            case Camera::FRONT:
                feedbackPos.x = x;
                feedbackPos.y = -y;
                break;
            
            case Camera::RIGHT:  
                feedbackPos.z = -x;          
                feedbackPos.y = -y;
                break;
             
            case Camera::USER:
                //TODO
                //la z corrisponde (pi� o meno) alla y quando camera->orientation.x > 20 e < 150
                //la z corrisponde (pi� o meno) alla x quando camera->orientation.y > 20 e < 150
                //si pu� fare una specia di interpolazione
                float c = cos (float (cameraOrient.y)) * sin (float (cameraOrient.x));
                float s = sin (float (cameraOrient.y)) * cos (float (cameraOrient.x));

                feedbackPos.x = x;
                feedbackPos.y = -y;
                feedbackPos.z = (-x*s + y*c);
                click.y -= FeedbackRotation::CIRCLE_RADIUS;
                break;
        }
    }

    UpdateViews();                                              //Updates the 3D views
}

/// <summary> 
/// Mouse Released event handler, called whenever the operation 
/// is in execution and a 3D view intercepts a mouse release event
/// </summary>
/// <param name="view"> The view that has thrown the event </param>
/// <param name="e"> Mouse Released event </param>
void CIOps::OP_Scale::MouseReleaseEvent (CIView::View* view, QMouseEvent* e) {
    if (drag.Length() > 0)
        status = COMPLETED;
    else
        status = ZOMBIE;

    CIView::View::Feedback_Scaling.Reset();                     //Reset the feedback axes coordinates of the four 3D views

    CIView::View::Feedback_Scaling.DeselectAll();
    UpdateViews();                                      //Updates the 3D views
}

/// <summary> 
/// Mouse Moved event handler, called whenever the operation 
/// is in execution and a 3D view intercepts a mouse move event
/// </summary>
/// <param name="view"> The view that has thrown the event </param>
/// <param name="e"> Mouse Moved event </param>
void CIOps::OP_Scale::MouseMoveEvent (CIView::View* view, QMouseEvent* e) {
    float x = e->x();
    float y = e->y();

    view->WindowToWorldCoordinates (&x, &y);            //Unproject the mouse cursor coordinates    

    //Computing the mouse drag value
    switch (view->GetCameraType()) {
        case Camera::TOP:
            drag.x = x - click.x;
            drag.z = y - click.z;
            break;
        
        case Camera::FRONT:
            drag.x = x - click.x;
            drag.y = click.y - y;
            break;
        
        case Camera::RIGHT:            
            drag.y = click.y - y;
            drag.z = click.z - x;
            break;

        case Camera::USER:          //TODO -> Consider the camera orientation on the z axis too!!!
            Orientation& cameraOrient = view->GetCameraOrientation();       //Gets the current camera orientation

            //Computing the drag on the x axis (depends on the x, y and z camera angles)
            if ((cameraOrient.y >= 300 || cameraOrient.y <= 60))
                drag.x = x - click.x;
            else if ((cameraOrient.y >=120 && cameraOrient.y <= 240))
                drag.x = click.x - x;
            else if ((cameraOrient.y > 60 && cameraOrient.y < 120)) {
                if (cameraOrient.x > 180 && cameraOrient.x < 360)
                    drag.x = y - click.y;
                else
                    drag.x = click.y - y;
            }
            else if ((cameraOrient.y < 300 && cameraOrient.y > 240)) {
                if (cameraOrient.x > 180 && cameraOrient.x < 360)
                    drag.x = click.y - y;
                else
                    drag.x = y - click.y;                
            }

            //Computing the drag on the y axis (depends on the x and z camera angles)
            if (cameraOrient.x >= 90 && cameraOrient.x <= 270)
                drag.y = y - click.y;
            else
                drag.y = click.y - y;

            //Computing the drag on the z-axis (depends on the x, y and z camera angles)
            if ((cameraOrient.y >= 300 || cameraOrient.y <= 60)) {
                if (cameraOrient.x > 180 && cameraOrient.x < 360)
                    drag.z = click.y - y;
                else
                    drag.z = y - click.y;
            }
            else if ((cameraOrient.y >=120 && cameraOrient.y <= 240)) {
                if (cameraOrient.x > 180 && cameraOrient.x < 360)
                    drag.z = y - click.y;
                else
                    drag.z = click.y - y;
            }
            else if ((cameraOrient.y > 60 && cameraOrient.y < 120))
                drag.z = x - click.x;
            else if ((cameraOrient.y < 300 && cameraOrient.y > 240))
                drag.z = click.x - x;
            break;
    }    

    //A factor used to translate the drag into scaling values (according to the current zoom value)
    float factor = 20.0f / view->GetCameraZoom();;

    //Tranforming drag x-value in scaling value
    scale.x = 1 + drag.x / factor;
    scale.y = 1 + drag.y / factor;
    scale.z = 1 + drag.z / factor;

    //Temporary matrix used to compute the final scaling tranformation
    Matrix toOrigin, toPosition, scaling;    
    toPosition.SetTranslation (feedbackPos.x, feedbackPos.y, feedbackPos.z);        //Sets up the translation to the final position            
    toOrigin.SetTranslation (-feedbackPos.x, -feedbackPos.y, -feedbackPos.z);       //Sets up the translation to the origin        

    //Setting up the scaling matrix according to the selected axes
    switch (selected) {
        case FeedbackScaling::X_AXIS:
            scaling.SetScaling (scale.x, 1, 1);     //Sets up the scaling transformation            
            break;

        case FeedbackScaling::Y_AXIS:
            scaling.SetScaling (1, scale.y, 1);     //Sets up the scaling transformation
            break;
            
        case FeedbackScaling::Z_AXIS:
            scaling.SetScaling (1, 1, scale.z);     //Sets up the scaling transformation
            break;

        case FeedbackScaling::ALL_AXES:
            float value;

            //Computing the scale value according to the current camera type
            switch (view->GetCameraType()) {
                case Camera::TOP:
                    //Uniform 2D scaling
                    value = scale.x * scale.z;
                    scale.x = value;
                    scale.z = value;
                    break;
                
                case Camera::FRONT:
                    //Uniform 2D scaling
                    value = scale.x * scale.y;
                    scale.x = value;
                    scale.y = value;
                    break;
                
                case Camera::RIGHT:        
                    //Uniform 2D scaling
                    value = scale.y * scale.z;
                    scale.y = value;
                    scale.z = value;
                    break;

                case Camera::USER:          
                    //In this case we have to re-compute the drag values, 
                    //whereas the drag in 2D, (because it's a uniform 3D scaling, 
                    //so the scaling value for the 3 axes is the same)
                    drag.x = x - click.x;
                    drag.y = click.y - y;

                    scale.x = 1 + drag.x / factor;
                    scale.y = 1 + drag.y / factor;

                    value = scale.x * scale.y;

                    scale.x = value;
                    scale.y = value;
                    scale.z = value;
                    break;
            }

            scaling.SetScaling (scale.x, scale.y, scale.z);     //Sets up the scaling transformation
            break;
    }
    scalingMatrix = toOrigin * scaling * toPosition;            //Computes the final matrix for the correct scaling transformation

    Matrix toApply;
    toApply = inverseMatrix*scalingMatrix;               //The matrix to apply for the transformation has to consider the previous transformation

    scene.ApplyMatrix (toApply);                                //Applies the transformation to the selected vertices

    //Computing the transformation for the visual feedback
    Matrix toApplyForFeedback;
    toApplyForFeedback.elements[ 0] = toApply.elements[ 0] != 1 
                      ? toApply.elements[ 0] * 0.99999f   
                      : 1;                                      //Assigns the x-axis scaling value 

    toApplyForFeedback.elements[ 5] = toApply.elements[ 5] != 1 
                                      ? toApply.elements[ 5] * 0.999f     
                                      : 1;                      //Assigns the y-axis scaling value 

    toApplyForFeedback.elements[10] = toApply.elements[10] != 1 
                                      ? toApply.elements[10] * 0.9999999f 
                                      : 1;                      //Assigns the z-axis scaling value 
    

    CIView::View::Feedback_Scaling.ApplyMatrix (toApplyForFeedback);    //Applies the matrix to the feedback axes coordinates of the four 3D views

    inverseMatrix = scalingMatrix.GetInverse();                 //Computes the new inverse matrix (used to undo the transformation)

    UpdateViews();                                              //Updates the 3D views
}

/// <summary> 
/// Wheel event handler, called whenever the operation 
/// is in execution and a 3D view intercepts a mouse wheel event
/// </summary>
/// <param name="view"> The view that has thrown the event </param>
/// <param name="e"> Wheel event </param>
void CIOps::OP_Scale::WheelEvent (CIView::View* view, QWheelEvent* e) {
}
/***************************************************************************************************************
***************************************** endregion Utility Methods ********************************************
****************************************************************************************************************/
